/*
 * Copyright (C) 2007 Eskil Bylund
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

using System;
using System.IO;

using DCSharp.Backend.Connections;
using DCSharp.Backend.Managers;
using DCSharp.Hashing;

namespace DCSharp.Backend.Objects
{
	public class UploadEventArgs : EventArgs
	{
		private Uploader uploader;

		public UploadEventArgs(Uploader uploader)
		{
			this.uploader = uploader;
		}

		public Uploader Uploader
		{
			get { return uploader; }
		}
	}

	// NOTE: The sending is currently handled by UserConnection.
	/// <summary>
	/// Handles the uploading of a single file.
	/// </summary>
	public class Uploader : IDisposable
	{
		public event EventHandler ActiveChanged;

		private ShareManager shareManager;
		private UserConnection connection;

		private TransferInfo transferInfo; // Stores the progress once the upload has started.
		private bool active;
		private FileInfo localFile;
		private long length;

		public Uploader(ShareManager shareManager, UserConnection connection)
		{
			if (shareManager == null)
			{
				throw new ArgumentNullException("shareManager");
			}
			if (connection == null)
			{
				throw new ArgumentNullException("connection");
			}
			this.shareManager = shareManager;
			this.connection = connection;
		}

		#region Properties

		public User User
		{
			get { return connection.RemoteUser; }
		}

		public UserConnection Connection
		{
			get { return connection; }
		}

		public Request Request
		{
			get { return transferInfo.Request; }
		}

		public FileInfo LocalFile
		{
			get { return localFile; }
		}

		public long FileSize
		{
			get { return length; }
		}

		public double Progress
		{
			get { return transferInfo.Progress; }
		}

		public bool Active
		{
			get { return active; }
			private set
			{
				active = value;
				OnActiveChanged();
			}
		}

		#endregion

		#region Methods

		internal bool HandleUploadRequest(UserConnection connection,
			UploadRequestEventArgs args)
		{
			Stream stream;
			if (args.Response == null &&
				CreateResponse(args.Request, out stream, out localFile))
			{
				args.Response = stream;
				connection.TransferStarted += OnTransferStarted;

				length = stream.Length;
				return true;
			}
			return false;
		}

		public void Stop()
		{
			if (Active)
			{
				connection.Disconnect();
			}
		}

		public TimeSpan GetRemainingTime()
		{
			if (!Active)
			{
				return TimeSpan.Zero;
			}
			long remainingBytes = transferInfo.Length - transferInfo.Position;
			int remainingSeconds = 0;

			double bps = connection.Monitor.UploadSpeed;
			if (bps > 0)
			{
				remainingSeconds = (int)(remainingBytes / bps);
			}
			return TimeSpan.FromSeconds(remainingSeconds);
		}

		public void Dispose()
		{
			connection.TransferStarted -= OnTransferStarted;
			connection.TransferCompleted -= OnTransferCompleted;
			connection.TransferAborted -= OnTransferAborted;

			GC.SuppressFinalize(this);
		}

		protected bool CreateResponse(Request request, out Stream stream,
			out FileInfo fileInfo)
		{
			stream = null;
			fileInfo = null;

			switch (request.Type)
			{
				case RequestType.File:
					FileRequest fileRequest = (FileRequest)request;

					string path = fileRequest.TTH != null ?
						shareManager.GetFullPathFromTTH(fileRequest.TTH) :
						shareManager.GetFullPath(fileRequest.Path);

					if (path != null)
					{
						fileInfo = new FileInfo(path);

						stream = fileInfo.OpenRead();
						stream.Position = fileRequest.Start;
						return true;
					}
					break;

				case RequestType.List:
					ListRequest listRequest = (ListRequest)request;

					shareManager.SaveFileList();

					fileInfo = new FileInfo(listRequest.Compressed ?
						shareManager.CompressedFileListPath : shareManager.FileListPath);

					stream = fileInfo.OpenRead();
					return true;

				case RequestType.HashTree:
					HashTreeRequest treeRequest = (HashTreeRequest)request;

					HashTree hashTree = shareManager.GetHashTree(treeRequest.TTH);
					if (hashTree != null)
					{
						stream = new HashTreeInputStream(hashTree);
						return true;
					}
					break;
			}
			throw new FileNotFoundException();
		}

		protected virtual void OnActiveChanged()
		{
			if (ActiveChanged != null)
			{
				ActiveChanged(this, EventArgs.Empty);
			}
		}

		#region Event Handlers

		private void OnTransferStarted(object obj, EventArgs args)
		{
			UserConnection connection = (UserConnection)obj;

			connection.TransferStarted -= OnTransferStarted;
			connection.TransferCompleted += OnTransferCompleted;
			connection.TransferAborted += OnTransferAborted;

			transferInfo = connection.TransferInfo;
			Active = true;
		}

		private void OnTransferCompleted(object obj, EventArgs args)
		{
			UserConnection connection = (UserConnection)obj;

			connection.TransferCompleted -= OnTransferCompleted;
			connection.TransferAborted -= OnTransferAborted;

			Active = false;
		}

		private void OnTransferAborted(object obj, EventArgs args)
		{
			UserConnection connection = (UserConnection)obj;

			connection.TransferCompleted -= OnTransferCompleted;
			connection.TransferAborted -= OnTransferAborted;

			Active = false;
		}

		#endregion

		#endregion
	}
}
